package com.leyou.item.service;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.leyou.common.domain.PageResult;
import com.leyou.common.exception.domain.ExceptionEnum;
import com.leyou.common.exception.domain.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.item.domain.Sku;
import com.leyou.item.domain.Spu;
import com.leyou.item.domain.SpuDetail;
import com.leyou.item.dto.CategoryDTO;
import com.leyou.item.dto.SkuDTO;
import com.leyou.item.dto.SpuDTO;
import com.leyou.item.dto.SpuDetailDTO;
import com.leyou.item.mapper.SkuMapper;
import com.leyou.item.mapper.SpuDetailMapper;
import com.leyou.item.mapper.SpuMapper;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import tk.mybatis.mapper.entity.Example;

import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import static com.leyou.common.constant.MQConstants.Exchange.ITEM_EXCHANGE_NAME;
import static com.leyou.common.constant.MQConstants.RoutingKey.ITEM_DOWN_KEY;
import static com.leyou.common.constant.MQConstants.RoutingKey.ITEM_UP_KEY;

/**
 * (propagation = Propagation.REQUIRED)
 * 事务的传播行为默认值，表示当前方法如果已经有事务，其他业务方法就直接加入到当前事务
 * 如果没有则开始一个新的事务。总结：有且只有一个事务。
 */
@Service
@Transactional
public class GoodsService {

    @Autowired
    private SpuMapper spuMapper;

    @Autowired
    private SpuDetailMapper spuDetailMapper;

    @Autowired
    private SkuMapper skuMapper;

    @Autowired
    private CategoryService categoryService;

    @Autowired
    private BrandService brandService;

    @Autowired
    private AmqpTemplate amqpTemplate;

    /**
     * Spu分页查询
     * @return
     */
    public PageResult<SpuDTO> spuPageQuery(Integer page, Integer rows, String key, Boolean saleable) {
        //设置分页参数
        PageHelper.startPage(page, rows);
        //封装查询条件
        Example example = new Example(Spu.class);
        Example.Criteria criteria = example.createCriteria();
        if(!StringUtils.isEmpty(key)){
            //添加查询条件
            criteria.andLike("name", "%"+key+"%");
        }
        //说明：mysql数据库中用tinyint表示布尔类型，非零都是true，零为false
        //java端只要是true的数据进入数据库就是1，为false则是0.
        if(saleable!=null){
            criteria.andEqualTo("saleable", saleable);
        }
        //分页查询
        List<Spu> list = spuMapper.selectByExample(example);
        //判断list是否为空
        if(CollectionUtils.isEmpty(list)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //得到PageHelper的分页对象
        PageInfo<Spu> pageInfo = new PageInfo<>(list);
        //将Spu的集合转成SpuDto的集合
        List<SpuDTO> spuDTOS = BeanHelper.copyWithCollection(pageInfo.getList(), SpuDTO.class);
        //给SpuDTO集合中的分类名称和品牌名称赋值
        handlerCategoryNameAndBrandName(spuDTOS);
        //封装自定义的分页对象
        PageResult<SpuDTO> pageResult = new PageResult<>(pageInfo.getTotal(), pageInfo.getPages(), spuDTOS);
        return pageResult;
    }

    /**
     * 给SpuDTO集合中的分类名称和品牌名称赋值
     * @param spuDTOS
     */
    private void handlerCategoryNameAndBrandName(List<SpuDTO> spuDTOS) {
        spuDTOS.forEach(spuDTO -> {
            //获取到当前spu对象的分类id的集合
            List<Long> categoryIds = spuDTO.getCategoryIds();
            String categoryNames = categoryService.findCategorysByCids(categoryIds)//根据分类id的集合查询出Category对象的集合
                    .stream()//将集合转成流
                    .map(CategoryDTO::getName)//开始收集每个Category对象中的name值
                    .collect(Collectors.joining("/"));//将所有获取到的name的集合收集为以/分割的字符串
            //将分类名称拼接好的字符串赋值给spudto
            spuDTO.setCategoryName(categoryNames);
            //根据品牌的id查询品牌对象的名称
            String brandName = brandService.findBrandById(spuDTO.getBrandId()).getName();
            //把品牌的名称赋值给spudto对象
            spuDTO.setBrandName(brandName);
        });
    }

    /**
     * 添加商品
     */
    public void saveGoods(SpuDTO spuDTO) {
        //添加Spu
        //将spuDto转成spu
        Spu spu = BeanHelper.copyProperties(spuDTO, Spu.class);
        if(spu==null){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //把商品设置为下架
        spu.setSaleable(false);
        //保存数据库
        int spuCount = spuMapper.insertSelective(spu);
        if(spuCount!=1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }

        //添加SpuDetail
        //得到SpuDetailDTO对象
        SpuDetailDTO spuDetailDTO = spuDTO.getSpuDetail();
        //将SpuDetailDTO转成SpuDetail
        SpuDetail spuDetail = BeanHelper.copyProperties(spuDetailDTO, SpuDetail.class);
        if(spuDetail==null){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //给SpuDetail的主键赋值
        spuDetail.setSpuId(spu.getId());
        //保存数据库
        int spuDetailCount = spuDetailMapper.insertSelective(spuDetail);
        if(spuDetailCount!=1){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }

        //添加Sku集合
        //获取到SkuDTO集合
        List<SkuDTO> skuDTOS = spuDTO.getSkus();
        //判空
        if(CollectionUtils.isEmpty(skuDTOS)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //将SkuDTO集合转成Sku集合
        List<Sku> skus = BeanHelper.copyWithCollection(skuDTOS, Sku.class);
        //给每个Sku对象赋值spuId
        skus.forEach(sku -> {
            sku.setSpuId(spu.getId());
            sku.setCreateTime(new Date());
            sku.setUpdateTime(new Date());
        });
        //保存sku集合
        int skuCount = skuMapper.insertList(skus);
        if(skuCount!=skus.size()){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }

    }

    /**
     * 商品的上下架
     * @param id
     * @param saleable
     */
    public void updateSaleable(Long id, Boolean saleable) {
        try {
            Spu record = new Spu();
            record.setId(id);
            record.setSaleable(saleable);
            spuMapper.updateByPrimaryKeySelective(record);

            //考虑静态页删除和添加，还有索引库中文档的删除和添加，这里选择使用异步请求
            //参数一String exchange交换机, 参数二String routingKey, 参数三final Object object 发送的消息
            String routingKey = saleable ? ITEM_UP_KEY : ITEM_DOWN_KEY;
            amqpTemplate.convertAndSend(ITEM_EXCHANGE_NAME, routingKey, id);
        }catch (Exception e){
            throw new LyException(ExceptionEnum.UPDATE_OPERATION_FAIL);
        }
    }

    public SpuDetailDTO findSpuDetailDTObyId(Long id) {
        SpuDetail spuDetail = spuDetailMapper.selectByPrimaryKey(id);
        if(spuDetail==null){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        return BeanHelper.copyProperties(spuDetail, SpuDetailDTO.class);
    }

    public List<SkuDTO> findSkusBySpuId(Long id) {
        Sku record = new Sku();
        record.setSpuId(id);
        List<Sku> skus = skuMapper.select(record);
        if(CollectionUtils.isEmpty(skus)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        return BeanHelper.copyWithCollection(skus, SkuDTO.class);
    }

    public void updateGoods(SpuDTO spuDTO) {
        //获取到Spu对象
        Spu spu = BeanHelper.copyProperties(spuDTO, Spu.class);
        //判空
        if(spu==null){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //更新Spu
        int spuCount = spuMapper.updateByPrimaryKeySelective(spu);
        if(spuCount!=1){
            throw new LyException(ExceptionEnum.UPDATE_OPERATION_FAIL);
        }

        //获取SpuDetailDTO对象
        SpuDetailDTO detailDTO = spuDTO.getSpuDetail();
        //判空
        if(detailDTO==null){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //把dto转成domain
        SpuDetail spuDetail = BeanHelper.copyProperties(detailDTO, SpuDetail.class);
        //更新SpuDetail
        int spuDetailCount = spuDetailMapper.updateByPrimaryKeySelective(spuDetail);
        if(spuDetailCount!=1){
            throw new LyException(ExceptionEnum.UPDATE_OPERATION_FAIL);
        }

        //删除Spu下所有Sku
        Sku record = new Sku();
        record.setSpuId(spuDTO.getId());
        skuMapper.delete(record);

        //添加Sku集合
        //获取到SkuDTO集合
        List<SkuDTO> skuDTOS = spuDTO.getSkus();
        //判空
        if(CollectionUtils.isEmpty(skuDTOS)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        //保存新的Sku
        List<Sku> skus = BeanHelper.copyWithCollection(skuDTOS, Sku.class);
        //给每个Sku对象赋值spuId
        skus.forEach(sku -> {
            sku.setSpuId(spu.getId());
            sku.setCreateTime(new Date());
            sku.setUpdateTime(new Date());
        });
        //保存sku集合
        int skuCount = skuMapper.insertList(skus);
        if(skuCount!=skus.size()){
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }
    }

    public SpuDTO findSpuById(Long id) {
        Spu spu = spuMapper.selectByPrimaryKey(id);
        if(spu==null){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        SpuDTO spuDTO = BeanHelper.copyProperties(spu, SpuDTO.class);
        //把SpuDetail对象封装到Spu对象中
        SpuDetailDTO spuDetailDTO = findSpuDetailDTObyId(spuDTO.getId());
        spuDTO.setSpuDetail(spuDetailDTO);
        //把Sku的集合封装到Spu对象中
        List<SkuDTO> skuDTOS = findSkusBySpuId(spuDTO.getId());
        spuDTO.setSkus(skuDTOS);
        return spuDTO;
    }

    public List<SkuDTO> findSkusByIds(List<Long> ids) {
        List<Sku> skus = skuMapper.selectByIdList(ids);
        if(CollectionUtils.isEmpty(skus)){
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        return BeanHelper.copyWithCollection(skus, SkuDTO.class);
    }

    /**
     * 减库存
     * 超卖问题说明：
     * synchronized只能锁线程，不能锁进程。
     * 应该锁数据库表
     * 悲观锁：排他性，认为异常一定会发生，上来就锁。永远单线程。select * from xx where id = 111 for update
     * 乐观锁：认为不一定会发生异常，依然支持多线程，但是最终肯定是只有一个成功。一般加版本号。
     *         如果只有一种固定操作，可以不加版本号。
     *         update tb_sku set stock=10 where id=1 and stock = 16
     *  如果当前字段是一个数字，而且我们只是确保数字不为负数即可的话。
     *  可以将当前字段改为无符号字段。
     *  UNSIGNED属性就是将数字类型无符号化，与C、C++这些程序语言中的unsigned含义相同。
     *  INT的类型范围是-2 147 483 648 ～ 2 147 483 647，
     *  INT UNSIGNED的范围类型就是0 ～ 4 294 967 295。
     *  如果减后是负数，则会报错。
     * @param map
     */
    public void minusStock(Map<Long, Integer> map) {
        //获取map的entry集合
        Set<Map.Entry<Long, Integer>> entries = map.entrySet();
        //遍历
        entries.forEach(entry->{
            //获取skuId
            Long skuId = entry.getKey();
            //获取减的数量
            Integer num = entry.getValue();
            Sku record = new Sku();
            record.setId(skuId);
            //查询原来的库存
            Sku sku = skuMapper.selectByPrimaryKey(skuId);
            //得到现在的库存
            record.setStock(sku.getStock()-num);
            skuMapper.updateByPrimaryKeySelective(record);
        });

    }
}
